package aiyh.utils.recordset;

import aiyh.utils.annotation.recordset.ParamMapper;
import aiyh.utils.annotation.recordset.SqlString;
import aiyh.utils.excention.BindingException;
import aiyh.utils.excention.MethodNotFindException;
import aiyh.utils.excention.ParseSqlException;
import aiyh.utils.sqlUtil.sqlResult.impl.PrepSqlResultImpl;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author EBU7-dev1-ayh
 * create 2021/12/19 0019 15:28
 */


public class SqlHandler {

    List<Object> sqlArgs = new ArrayList<>();

    public PrepSqlResultImpl handler(String sql, boolean custom, Method method, Object[] args) {
        String findSql = findSql(sql, custom, method, args);
        Map<String, Object> methodArgNameMap = buildMethodArgNameMap(method, args);
//              处理基本类型以及包装类
        String parse;
        if (methodArgNameMap.size() == 0) {
            return new PrepSqlResultImpl(findSql, sqlArgs);
        }
        if (methodArgNameMap.size() == 1) {
                Optional<Object> first = methodArgNameMap.values().stream().findFirst();
                parse = parse(findSql, first.get());
        } else {
            parse = parse(findSql, methodArgNameMap);
        }
        return new PrepSqlResultImpl(parse, sqlArgs);
    }

    private int findArg(Method method) {
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i ++) {
            Parameter parameter = parameters[i];
            SqlString annotation = parameter.getAnnotation(SqlString.class);
            if(annotation == null){
                return i;
            }
        }
        throw new BindingException("Wrong parameter annotation, cannot have two SQL string annotations！");
    }


    private String findSql(String sql, boolean custom, Method method, Object[] args) {
        String parsing = sql;
        if (custom) {
//            自定义
            Parameter[] parameters = method.getParameters();
            try {
                if (parameters.length == 0) {
                    throw new BindingException("cant not find sql parameter! this parameters is nothing!");
                }
            } catch (NullPointerException e) {
                throw new BindingException("cant not find sql parameter! this parameters is nothing!");
            }
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                Object arg = args[i];
                Class<?> type = parameter.getType();
                SqlString sqlAnnotation = parameter.getAnnotation(SqlString.class);
                if (sqlAnnotation != null && type.equals(String.class)) {
                    try {
                        parsing = arg.toString();
                    } catch (NullPointerException e) {
                        throw new BindingException("sql String param is null!");
                    }
                    break;
                }
            }
        }
        return parsing;
    }

    private Map<String, Object> buildMethodArgNameMap(Method method, Object[] args) {
        Parameter[] parameters = method.getParameters();
        Map<String, Object> params = new HashMap<>();
//      参数处理
        try {
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                Object arg = args[i];
                String name = parameter.getName();
                Class<?> type = parameter.getType();
                SqlString sqlAnnotation = parameter.getAnnotation(SqlString.class);
                ParamMapper paramAnnotation = parameter.getAnnotation(ParamMapper.class);
                if (sqlAnnotation != null && type.equals(String.class)) {
                    continue;
                }
                if (paramAnnotation != null) {
                    params.put(paramAnnotation.value(), arg);
                    continue;
                }
                params.put(name, arg);
            }
        } catch (NullPointerException e) {
            throw new BindingException("cant not find sql parameter! this parameters is nothing!");
        }
        return params;
    }

    private String parse(String sql, Object arg) {
        String parseSql = sql;
        String pattern = "\\$\\{\\s*(?<regx>(?<field>\\w+)(\\.?(?<other>\\S+))*)\\s*}";
        Pattern compile = Pattern.compile(pattern);
        Matcher matcher = compile.matcher(parseSql);
        while (matcher.find()) {
            String regx = matcher.group("regx");
            String field = matcher.group("field");
            String other = matcher.group("other");
            Object value = getValueByRegx(regx, field, other, arg, false);
            parseSql = parseSql.replaceFirst(pattern, value.toString());
        }
        pattern = "#\\{\\s*(?<regx>(?<field>\\w+)(\\.?(?<other>\\S+))*)\\s*}";
        compile = Pattern.compile(pattern);
        matcher = compile.matcher(parseSql);
        while (matcher.find()) {
            String regx = matcher.group("regx");
            String field = matcher.group("field");
            String other = matcher.group("other");
            Object value = getValueByRegx(regx, field, other, arg, true);
            parseSql = parseSql.replaceFirst(pattern, "?");
            sqlArgs.add(value);
        }
        return parseSql;
    }

    private Object getValueByRegx(String regx, String field, String other, Object arg, boolean isEscape) {
        if (!regx.contains(".")) {
            return valueHandler(arg, field, isEscape);
        }
        String pattern = "(?<field>\\w+)\\.*(?<other>(\\S+)*)";
        Pattern compile = Pattern.compile(pattern);
        Matcher matcher = compile.matcher(other);
        Object o = valueHandler(arg, field, isEscape);
        if (matcher.find()) {
            String innerField = matcher.group("field");
            String innerOther = matcher.group("other");
            return getValueByRegx(other, innerField, innerOther, o, isEscape);
        }
//        return getValueByRegx(other,)
        throw new BindingException("Unable to find value " + other);
    }


    private Object valueHandler(Object arg, String key, boolean isEscape) {
        if (arg instanceof Number) {
//                    处理数字类型
            return arg;
        }
        if (arg instanceof Boolean) {
//                    处理布尔类型
            return arg;
        }
        if (arg instanceof Character || arg instanceof String) {
//                    处理字符类型
            if (isEscape) {
                return arg;
            } else {
                return "'" + arg + "'";
            }
        }
        if (arg.getClass().isArray()) {
//            throw new TypeNonsupportException("A value is expected, but a set is received！");
            return StringUtils.join((Object[]) arg, ",");
        }
        //                判断参数类型
        if (arg instanceof Collection) {
//                    list
//            throw new TypeNonsupportException("A value is expected, but a set is received！");
            return StringUtils.join((Collection<?>)arg, ",");
        }
        if (arg instanceof Map) {
//                    map
            if (!((Map<?, ?>) arg).containsKey(key)) {
                throw new ParseSqlException("Failed to find {" + key + "} related field after parsing exception!");
            }
            Object o = ((Map<?, ?>) arg).get(key);
            if (o instanceof Character || o instanceof String) {
//                    处理字符类型
                if (isEscape) {
                    return o;
                } else {
                    return "'" + o + "'";
                }
            }
            return o;
        }
        String methodName = "get" + key.substring(0, 1).toUpperCase() + key.substring(1);
//        当做javaBean处理
        try {

            Method method = arg.getClass().getMethod(methodName);
            Object invoke = method.invoke(arg);
            if (invoke instanceof Character || invoke instanceof String) {
//                    处理字符类型
                if (isEscape) {
                    return invoke;
                } else {
                    return "'" + invoke + "'";
                }
            }
            return invoke;
        } catch (NoSuchMethodException e) {
//            e.printStackTrace();
            throw new MethodNotFindException(methodName + " is not find!");
        } catch (InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException(e.toString());
        }
    }
}
